#include "ops.h"

#include "dir.h"
#include "path.h"
#include <string>
#include <stack>
#include <memory>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/statvfs.h>
#include <sys/sendfile.h>
#include <utime.h>


namespace evm { namespace fs {

string absolute(const string& p_str, const string& base_str) {
    path p = p_str;
    path base = base_str;
    const bool has_root_dir = p.has_root_directory();
    const bool has_root_name = p.has_root_name();
    path abs;
    if (has_root_dir && has_root_name)
        abs = p;
    else {
        abs = base.is_absolute() ? base : path(absolute(base));
        if (has_root_dir)
            abs = abs.root_name() / p;
        else if (has_root_name)
            abs = p.root_name() / abs.root_directory() / abs.relative_path()
                  / p.relative_path();
        else
            abs = abs / p;
    }
    return abs;
}

inline bool is_dot(char c) { return c == '.'; }

inline bool is_dot(const string& path) {
    return path.size() == 1 && is_dot(path[0]);
}

inline bool is_dotdot(const string& path)
{
    return path.size() == 2 && is_dot(path[0]) && is_dot(path[1]);
}

struct free_as_in_malloc
{
    void operator()(void* p) const { ::free(p); }
};

using char_ptr = std::unique_ptr<char[], free_as_in_malloc>;

string read_symlink(const string& p, error_code& ec);

string canonical(const string& p, const string& base, error_code& ec) {
    const path pa = absolute(p, base);
    path result;

    char_ptr buf{nullptr};
    if (char* rp = ::realpath(pa.c_str(), nullptr)) {
        buf.reset(rp);
        result.assign(rp);
        ec.clear();
        return result;
    }

    if (errno != ENAMETOOLONG) {
      ec.assign(errno, std::generic_category());
      return result;
    }

    if (!exists(pa.c_str(), ec))
        return result;
    // else: we know there are (currently) no unresolvable symlink loops

    result = pa.root_path();

    deque<path> cmpts;
    for (auto& f : pa.relative_path())
        cmpts.push_back(f);

    int max_allowed_symlinks = 40;

    while (!cmpts.empty() && !ec) {
        path f = std::move(cmpts.front());
        cmpts.pop_front();

        if (is_dot(f)) {
            if (!is_directory(result.c_str(), ec) && !ec)
                ec.assign(ENOTDIR, std::generic_category());
        }
        else if (is_dotdot(f.c_str())) {
            auto parent = result.parent_path();
            if (parent.empty())
                result = pa.root_path();
            else
                result.swap(parent);
        }
        else {
            result /= f;

            if (is_symlink(result.c_str(), ec)) {
                path link = read_symlink(result.c_str(), ec);
                if (!ec) {
                    if (--max_allowed_symlinks == 0)
                        ec.assign(ELOOP, std::generic_category());
                    else {
                        if (link.is_absolute()) {
                            result = link.root_path();
                            link = link.relative_path();
                        }
                        else
                            result.remove_filename();

                        cmpts.insert(cmpts.begin(), link.begin(), link.end());
                    }
                }
            }
        }
    }

    if (ec || !exists(result.c_str(), ec))
        result.clear();

    return result;
}

string canonical(const string& p, const string& base, bool raise) {
    error_code ec;
    auto ret = canonical(p, base, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot canonicalize", p, ec);
    }
    return ret;
}

template<typename Bitmask>
inline bool is_set(Bitmask obj, Bitmask bits) {
    return (obj & bits) != Bitmask::none;
}

typedef struct ::stat stat_type;

inline file_type make_file_type(const stat_type& st) {
    switch (st.st_mode & S_IFMT) {
        case S_IFREG:
            return file_type::regular;
        case S_IFDIR:
            return file_type::directory;
        case S_IFCHR:
            return file_type::character;
        case S_IFBLK:
            return file_type::block;
        case S_IFIFO:
            return file_type::fifo;
        case S_IFSOCK:
            return file_type::socket;
        case S_IFLNK:
            return file_type::symlink;
        default:
            return file_type::unknown;
    }
}

inline file_status make_file_status(const stat_type& st) {
    return file_status{
        make_file_type(st),
        static_cast<perms>(st.st_mode) & perms::mask
    };
}

inline bool is_not_found_errno(int err) {
    return err == ENOENT || err == ENOTDIR;
}

inline file_time_type file_time(const stat_type& st) {
    using namespace std::chrono;
    auto duration = seconds(st.st_mtim.tv_sec) + nanoseconds(st.st_mtim.tv_nsec);
    return fs::file_time_type(duration_cast<microseconds>(duration));
}

bool do_copy_file(const string& from, const string& to,
             fs::copy_options option,
             stat_type* from_st, stat_type* to_st,
             std::error_code& ec) noexcept
{
    stat_type st1, st2;
    file_status f, t;

    if (to_st == nullptr) {
        if (::stat(to.c_str(), &st1)) {
            if (!is_not_found_errno(errno)) {
                ec.assign(errno, std::generic_category());
                return false;
            }
        }
        else
            to_st = &st1;
    }
    else if (to_st == from_st)
        to_st = nullptr;

    if (to_st == nullptr)
        t = file_status{file_type::not_found};
    else
        t = make_file_status(*to_st);

    if (from_st == nullptr)
    {
        if (::stat(from.c_str(), &st2)) {
            ec.assign(errno, std::generic_category());
            return false;
        }
        else
            from_st = &st2;
    }
    f = make_file_status(*from_st);

    if (exists(t)) {
        if (!is_other(t) && !is_other(f)
            && to_st->st_dev == from_st->st_dev
            && to_st->st_ino == from_st->st_ino) {
            ec = std::make_error_code(std::errc::file_exists);
            return false;
        }

        if (is_set(option, copy_options::skip_existing)) {
            ec.clear();
            return false;
        }
        else if (is_set(option, copy_options::update_existing)) {
            if (file_time(*from_st) <= file_time(*to_st)) {
                ec.clear();
                return false;
            }
        }
        else if (!is_set(option, copy_options::overwrite_existing)) {
            ec = std::make_error_code(std::errc::file_exists);
            return false;
        }
    }

    struct CloseFD {
        ~CloseFD() { if (fd != -1) ::close(fd); }
        int fd;
    };

    CloseFD in = { ::open(from.c_str(), O_RDONLY) };
    if (in.fd == -1) {
        ec.assign(errno, std::generic_category());
        return false;
    }
    int oflag = O_WRONLY | O_CREAT;
    if (is_set(option, copy_options::overwrite_existing | copy_options::update_existing))
        oflag |= O_TRUNC;
    else
        oflag |= O_EXCL;
    CloseFD out = { ::open(to.c_str(), oflag, S_IWUSR) };
    if (out.fd == -1) {
        if (errno == EEXIST && is_set(option, copy_options::skip_existing))
            ec.clear();
        else
            ec.assign(errno, std::generic_category());
        return false;
    }

    auto n = ::sendfile(out.fd, in.fd, nullptr, from_st->st_size);
    if (n != from_st->st_size) {
        ec.assign(errno, std::generic_category());
        return false;
    }


    if (::chmod(to.c_str(), from_st->st_mode)) {
        ec.assign(errno, std::generic_category());
        return false;
    }
    ec.clear();
    return true;
}

void copy(const string& from, const string& to, copy_options options, error_code& ec) {
    bool skip_symlinks = is_set(options, copy_options::skip_symlinks);
    bool create_symlinks = is_set(options, copy_options::create_symlinks);
    bool use_lstat = create_symlinks || skip_symlinks;

    file_status f, t;
    stat_type from_st, to_st;
    if (use_lstat ? ::lstat(from.c_str(), &from_st) : ::stat(from.c_str(), &from_st)) {
        ec.assign(errno, std::generic_category());
        return;
    }

    if (use_lstat ? ::lstat(from.c_str(), &from_st) : ::stat(from.c_str(), &from_st)) {
        if (!is_not_found_errno(errno)) {
            ec.assign(errno, std::generic_category());
            return;
        }
        t = file_status{file_type::not_found};
    }
    else
        t = make_file_status(to_st);
    f = make_file_status(from_st);

    if (exists(t) && !is_other(t) && !is_other(f)
        && to_st.st_dev == from_st.st_dev && to_st.st_ino == from_st.st_ino) {
        ec = std::make_error_code(std::errc::file_exists);
        return;
    }
    if (is_other(f) || is_other(t)) {
        ec = std::make_error_code(std::errc::not_supported);
        return;
    }
    if (is_directory(f) && is_regular_file(t)) {
        ec = std::make_error_code(std::errc::is_a_directory);
        return;
    }

    if (is_symlink(f)) {
        if (skip_symlinks)
            ec.clear();
        else if (!exists(t) && is_set(options, copy_options::copy_symlinks))
            copy_symlink(from, to, ec);
        else
            // Not clear what should be done here.
            // "Otherwise report an error as specified in Error reporting (7)."
            ec = std::make_error_code(std::errc::invalid_argument);
    }
    else if (is_regular_file(f)) {
        if (is_set(options, copy_options::directories_only))
            ec.clear();
        else if (create_symlinks)
            create_symlink(from, to, ec);
        else if (is_set(options, copy_options::create_hard_links))
            create_hard_link(from, to, ec);
        else if (is_directory(t))
            do_copy_file(from, path(to) / from, options, &from_st, 0, ec);
        else {
            auto ptr = exists(t) ? &to_st : &from_st;
            do_copy_file(from, to, options, &from_st, ptr,  ec);
        }
    }
    else if (is_directory(f) && (is_set(options, copy_options::recursive)
                                 || options == copy_options::none)) {
        if (!exists(t))
            if (!create_directory(to, from, ec))
                return;
        // set an unused bit in options to disable further recursion
        if (!is_set(options, copy_options::recursive))
            options |= static_cast<copy_options>(4096);
        for (const directory_entry& x : directory_iterator(from))
            copy(x.path(), to/x.path().filename(), options, ec);
    }
    // "Otherwise no effects." (should ec.clear() be called?)
}

bool copy(const string& from, const string& to, copy_options options, bool raise) {
    error_code ec;
    copy(from, to, options, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot copy", from, to, ec);
        return false;
    }
    return true;
}

bool copy_file(const string& from, const string& to, copy_options options, bool raise) {
    error_code ec;
    do_copy_file(from, to, options, nullptr, nullptr, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot copy file", from, to, ec);
        return false;
    }
    return true;
}

void copy_symlink(const string& existing_symlink, const string& new_symlink, error_code& ec) {
    auto p = read_symlink(existing_symlink, ec);
    if (ec.value())
        return;
    create_symlink(p, new_symlink, ec);
}

bool copy_symlink(const string& existing_symlink, const string& new_symlink, bool raise) {
    error_code ec;
    copy_symlink(existing_symlink, new_symlink, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot copy symlink", existing_symlink, new_symlink, ec);
        return false;
    }
    return true;
}

string read_symlink(const string& p, error_code& ec) {
    stat_type st;
    if (::lstat(p.c_str(), &st)) {
        ec.assign(errno, std::generic_category());
        return {};
    }
    std::string buf(static_cast<size_t>(st.st_size), '\0');
    ssize_t len = ::readlink(p.c_str(), &buf.front(), buf.size());
    if (len == -1) {
        ec.assign(errno, std::generic_category());
        return {};
    }
    return {buf.data(), buf.data() + len};
}

string read_symlink(const string& p, bool raise) {
    error_code ec;
    string tgt = read_symlink(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("read_symlink", p, ec);
        return {};
    }
    return tgt;
}

void create_symlink(const string& to, const string& new_symlink, error_code& ec) {
    if (::symlink(to.c_str(), new_symlink.c_str()))
        ec.assign(errno, std::generic_category());
    else
        ec.clear();
}

bool create_symlink(const string& to, const string& new_symlink, bool raise) {
    error_code ec;
    create_symlink(to, new_symlink, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot create symlink", to, new_symlink, ec);
        return false;
    }
    return true;
}

bool create_directories(const string& p_str, error_code& ec) noexcept {
    path p = p_str;

    if (p.empty()) {
        ec = std::make_error_code(std::errc::invalid_argument);
        return false;
    }

    std::stack<path> missing;
    path pp = p;

    while (!pp.empty() && status(pp.c_str(), ec).type() == file_type::not_found) {
        ec.clear();
        const auto& filename = pp.filename();
        if (!is_dot(filename) && !is_dotdot(filename))
            missing.push(pp);
        pp.remove_filename();
    }

    if (ec || missing.empty())
        return false;

    do {
        string top = missing.top();
        create_directory(top, ec);
        if (ec && is_directory(top))
            ec.clear();
        missing.pop();
    }
    while (!missing.empty() && !ec);

    return missing.empty();
}

bool create_directories(const string& p, bool raise) {
    error_code ec;
    bool ret = create_directories(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot create directories", p, ec);
    }
    return ret;
}

bool create_dir(const string& p, perms perm, error_code& ec)
{
    ::mode_t mode = static_cast<std::underlying_type<perms>::type>(perm);
    if (::mkdir(p.c_str(), mode)) {
        ec.assign(errno, std::generic_category());
        return false;
    }
    else {
        ec.clear();
        return true;
    }
}

bool create_directory(const string& p, error_code& ec) {
    return create_dir(p, perms::all, ec);
}

bool create_directory(const string& p, bool raise) {
    error_code ec;
    bool ret = create_directory(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot create directory", p, ec);
    }
    return ret;
}

bool create_directory(const string& p, const string& attributes, error_code& ec) {
    stat_type st;
    if (::stat(attributes.c_str(), &st)) {
        ec.assign(errno, std::generic_category());
        return false;
    }

    return create_dir(p, static_cast<perms>(st.st_mode), ec);
}

bool create_directory(const string& p, const string& attributes, bool raise) {
    error_code ec;
    bool ret = create_directory(p, attributes, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot create directory", p, ec);
    }
    return ret;
}

bool create_directory_symlink(const string& to, const string& new_symlink, bool raise) {
    return create_symlink(to, new_symlink, raise);
}

void create_hard_link(const string& to, const string& new_hard_link, error_code& ec) {
    if (::link(to.c_str(), new_hard_link.c_str()))
        ec.assign(errno, std::generic_category());
    else
        ec.clear();
}

bool create_hard_link(const string& to, const string& new_hard_link, bool raise) {
    error_code ec;
    create_hard_link(to, new_hard_link, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot create hard link", to, new_hard_link, ec);
        return false;
    }
    return true;
}

string current_path(error_code& ec) {
    string p;
    if (char_ptr cwd = char_ptr{::getcwd(nullptr, 0)}) {
        p = cwd.get();
        ec.clear();
    }
    else
        ec.assign(errno, std::generic_category());
    return p;
}

string current_path(bool raise) {
    error_code ec;
    string p = current_path(ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot get current path", ec);
    }
    return p;
}

void current_path(const string& p, error_code& ec) {
    if (::chdir(p.c_str()))
        ec.assign(errno, std::generic_category());
    else
        ec.clear();
}

bool current_path(const string& p, bool raise) {
    error_code ec;
    current_path(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot set current path", ec);
        return false;
    }
    return true;
}

bool equivalent(const string& p1, const string& p2, error_code& ec) {
    stat_type st1, st2;
    if (::stat(p1.c_str(), &st1) == 0 && ::stat(p2.c_str(), &st2) == 0)
    {
        file_status s1 = make_file_status(st1);
        file_status s2 = make_file_status(st2);
        if (is_other(s1) && is_other(s2))
        {
            ec = std::make_error_code(std::errc::not_supported);
            return false;
        }
        ec.clear();
        return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
    }
    else if (is_not_found_errno(errno))
    {
        ec = std::make_error_code(std::errc::no_such_file_or_directory);
        return false;
    }
    ec.assign(errno, std::generic_category());
    return false;
}

bool equivalent(const string& p1, const string& p2, bool raise) {
    error_code ec;
    bool ret = equivalent(p1, p2, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot check file equivalence", p1, p2, ec);
    }
    return ret;
}

template<typename Accessor, typename T>
inline T do_stat(const string& p, error_code& ec, Accessor f, T default_) {
    stat_type st;
    if (::stat(p.c_str(), &st)) {
        ec.assign(errno, std::generic_category());
        return default_;
    }
    ec.clear();
    return f(st);
}

uintmax_t file_size(const string& p, error_code& ec) {
    struct S {
        S(const stat_type& st) : type(make_file_type(st)), size(st.st_size) { }
        S() : type(file_type::not_found) { }
        file_type type;
        size_t size;
    };
    auto s = do_stat(p, ec, [](const stat_type& st) { return S{st}; }, S{});
    if (s.type == file_type::regular)
        return s.size;

    if (!ec) {
        if (s.type == file_type::directory)
            ec = std::make_error_code(std::errc::is_a_directory);
        else
            ec = std::make_error_code(std::errc::not_supported);
    }
    return static_cast<uintmax_t>(-1);
}

uintmax_t file_size(const string& p, bool raise) {
    error_code ec;
    auto ret = file_size(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot get file size", p, ec);
    }
    return ret;
}

uintmax_t hard_link_count(const string& p, error_code& ec) {
    return do_stat(p, ec, std::mem_fn(&stat_type::st_nlink), static_cast<uintmax_t>(-1));
}

uintmax_t hard_link_count(const string& p, bool raise) {
    error_code ec;
    auto ret = hard_link_count(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot get link count", p, ec);
    }
    return ret;
}

bool is_empty(const string& p, error_code& ec) {
    auto s = status(p, ec);
    if (ec.value())
        return false;
    return is_directory(s)
           ? directory_iterator(p, ec) == directory_iterator()
           : file_size(p, ec) == 0;
}

bool is_empty(const string& p, bool raise) {
    error_code ec;
    bool ret = is_empty(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("is empty", p, ec);
    }
    return ret;
}

file_time_type last_write_time(const string& p, error_code& ec) {
    return do_stat(p, ec, [](const stat_type& st) { return file_time(st); },
                   file_time_type::min());
}

file_time_type last_write_time(const string& p, bool raise) {
    error_code ec;
    auto ret = last_write_time(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot get file time", p, ec);
    }
    return ret;
}

void last_write_time(const string& p, file_time_type new_time, error_code& ec){
    using namespace std::chrono;
    auto d = new_time.time_since_epoch();
    auto s = duration_cast<seconds>(d);
    auto ns = duration_cast<nanoseconds>(d - s);

    struct ::timespec ts[2];
    ts[0].tv_sec = 0;
    ts[0].tv_nsec = UTIME_OMIT;
    ts[1].tv_sec = static_cast<std::time_t>(s.count());
    ts[1].tv_nsec = static_cast<long>(ns.count());

    if (::utimensat(AT_FDCWD, p.c_str(), ts, 0))
        ec.assign(errno, std::generic_category());
    else
        ec.clear();
}

bool last_write_time(const string& p, file_time_type new_time, bool raise) {
    error_code ec;
    last_write_time(p, new_time, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot set file time", p, ec);
        return false;
    }
    return true;
}

bool access(const string& p, Mode mode, bool raise) {
    int m{};
    switch (mode) {
        case READ:
            m = R_OK;
            break;
        case WRITE:
            m = W_OK;
            break;
        case EXECUTE:
            m = X_OK;
            break;
        default:
            break;
    }
    if (::access(p.c_str(), m)) {
        if (errno != EACCES && raise)
            throw filesystem_error("cannot access", p,
                                   std::error_code(errno, std::generic_category()));
        return false;
    }
    return true;
}

void permissions(const string& p, perms prms, error_code& ec) {
    if (::chmod(p.c_str(), static_cast<mode_t>(prms)))
        ec.assign(errno, std::generic_category());
    else
        ec.clear();
}

bool permissions(const string& p, perms prms, bool raise) {
    error_code ec;
    permissions(p, prms, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot set permissions", p, ec);
        return false;
    }
    return true;
}

bool remove(const string& p, error_code& ec) {
    if (exists(symlink_status(p, ec)))
    {
        if (::remove(p.c_str()) == 0)
        {
            ec.clear();
            return true;
        }
        else
            ec.assign(errno, std::generic_category());
    }
    return false;
}

bool remove(const string& p, bool raise) {
    error_code ec;
    bool ret = remove(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot remove", p, ec);
    }
    return ret;
}

uintmax_t remove_all(const string& p, error_code& ec) {
    auto fs = symlink_status(p, ec);
    uintmax_t count = 0;
    if (ec.value() == 0 && fs.type() == file_type::directory)
        for (directory_iterator d(p, ec), end; ec.value() == 0 && d != end; ++d)
            count += fs::remove(d->path(), ec);
    if (ec.value())
        return static_cast<uintmax_t>(-1);
    return fs::remove(p, ec) ? ++count : static_cast<uintmax_t>(-1);  // fs:remove() calls ec.clear()
}

uintmax_t remove_all(const string& p, bool raise) {
    error_code ec;
    auto ret = remove_all(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot remove all", p, ec);
    }
    return ret;
}

void rename(const string& from, const string& to, error_code& ec) {
    if (::rename(from.c_str(), to.c_str()))
        ec.assign(errno, std::generic_category());
    else
        ec.clear();
}

bool rename(const string& from, const string& to, bool raise) {
    error_code ec;
    rename(from, to, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot rename", from, to, ec);
        return false;
    }
    return true;
}

void resize_file(const string& p, uintmax_t size, error_code& ec) {
    if (size > static_cast<uintmax_t>(std::numeric_limits<off_t>::max()))
        ec.assign(EINVAL, std::generic_category());
    else if (::truncate(p.c_str(), size))
        ec.assign(errno, std::generic_category());
    else
        ec.clear();
}

bool resize_file(const string& p, uintmax_t size, bool raise) {
    error_code ec;
    resize_file(p, size, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot resize file", p, ec);
        return false;
    }
    return true;
}

space_info space(const string& p, error_code& ec) {
    space_info info = {
            static_cast<uintmax_t>(-1),
            static_cast<uintmax_t>(-1),
            static_cast<uintmax_t>(-1)
    };

    struct ::statvfs f;
    if (::statvfs(p.c_str(), &f))
        ec.assign(errno, std::generic_category());
    else {
        info = space_info{
                f.f_blocks * f.f_frsize,
                f.f_bfree * f.f_frsize,
                f.f_bavail * f.f_frsize
        };
        ec.clear();
    }

    return info;
}

space_info space(const string& p, bool raise) {
    error_code ec;
    auto ret = space(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("cannot get space", p, ec);
    }
    return ret;
}

file_status status(const string& p, error_code& ec) {
    file_status status;
    stat_type st;
    if (::stat(p.c_str(), &st)) {
        ec.assign(errno, std::generic_category());

        if (is_not_found_errno(errno))
            status.type(file_type::not_found);
    }
    else {
        status = make_file_status(st);
        ec.clear();
    }
    return status;
}

file_status status(const string& p, bool raise) {
    error_code ec;
    auto ret = status(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("status", p, ec);
    }
    return ret;
}

file_status symlink_status(const string& p, error_code& ec) {
    file_status status;
    stat_type st;
    if (::lstat(p.c_str(), &st)) {
        ec.assign(errno, std::generic_category());

        if (is_not_found_errno(errno))
            status.type(file_type::not_found);
    }
    else {
        status = make_file_status(st);
        ec.clear();
    }
    return status;
}

file_status symlink_status(const string& p, bool raise) {
    error_code ec;
    auto ret = symlink_status(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("symlink_status", p, ec);
    }
    return ret;
}

string system_complete(const string& p, error_code& ec) {
    path base = current_path(ec);
    if (ec.value())
        return {};
    return absolute(p, base);
}

string system_complete(const string& p, bool raise) {
    error_code ec;
    auto ret = system_complete(p, ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("system_complete", p, ec);
    }
    return ret;
}

string temp_directory_path(error_code& ec) {
    const char* tmpdir = nullptr;
    const char* env[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr};
    for (auto e = env; tmpdir == nullptr && *e != nullptr; ++e)
        tmpdir = ::getenv(*e);
    path p = tmpdir ? tmpdir : "/tmp";
    if (exists(p) && is_directory(p)) {
        ec.clear();
        return p;
    }
    ec = std::make_error_code(std::errc::not_a_directory);
    return {};
}

string temp_directory_path(bool raise) {
    error_code ec;
    auto ret = temp_directory_path(ec);
    if (ec.value()) {
        if (raise)
            throw filesystem_error("temp_directory_path", ec);
    }
    return ret;
}

}}
